using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace Exif.Various
{
	internal class ExpandableTDP : TypeDescriptionProvider
	{
		private static readonly TypeDescriptionProvider _defaultTypeProvider =
			TypeDescriptor.GetProvider(typeof(object));

		public ExpandableTDP()
			: base(_defaultTypeProvider)
		{
		}

		public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
		{
			var defaultDescriptor = base.GetTypeDescriptor(objectType, instance);
			return /*instance == null ? defaultDescriptor :*/ new ExpandableTD(defaultDescriptor, instance);
		}

		internal class ExpandableTD : CustomTypeDescriptor
		{
			internal class ExpandablePD : PropertyDescriptor
			{
				private readonly PropertyDescriptor _descr;

				public ExpandablePD(string name, Attribute[] attrs)
					: base(name, attrs)
				{
					this.ExpandAttributes(attrs);
				}

				public ExpandablePD(MemberDescriptor descr)
					: base(descr)
				{
					this._descr = descr as PropertyDescriptor;
					this.ExpandAttributes(this.AttributeArray);
				}

				public ExpandablePD(MemberDescriptor descr, Attribute[] attrs)
					: base(descr, attrs)
				{
					this.ExpandAttributes(this.AttributeArray);
				}

				private void ExpandAttributes(IEnumerable<Attribute> attrs)
				{
					var lst = attrs != null ? new List<Attribute>(attrs) : new List<Attribute>(1);
					lst.Add(new TypeConverterAttribute(typeof(ExpandableObjectConverter)));
					this.AttributeArray = lst.ToArray();
				}

				#region Overrides of PropertyDescriptor

				public override bool CanResetValue(object component)
				{
					return this._descr != null && this._descr.CanResetValue(component);
				}

				public override object GetValue(object component)
				{
					if(component == null)
					{
						throw new ArgumentNullException("component");
					}
					return this._descr != null ? this._descr.GetValue(component) : null;
				}

				public override void ResetValue(object component)
				{
					if (component == null)
					{
						throw new ArgumentNullException("component");
					}
					if (this._descr != null)
					{
						this._descr.ResetValue(component);
					}
				}

				public override void SetValue(object component, object value)
				{
					if (component == null)
					{
						throw new ArgumentNullException("component");
					}
					if (this._descr != null)
					{
						this._descr.SetValue(component, value);
					}
				}

				public override bool ShouldSerializeValue(object component)
				{
					if (component == null)
					{
						throw new ArgumentNullException("component");
					}
					return this._descr != null ? this._descr.ShouldSerializeValue(component) : false;
				}

				public override Type ComponentType
				{
					get
					{
						return this._descr != null ? this._descr.ComponentType : typeof(object);
					}
				}

				public override bool IsReadOnly
				{
					get
					{
						return this._descr != null ? this._descr.IsReadOnly : true;
					}
				}

				public override Type PropertyType
				{
					get
					{
						return this._descr != null ? this._descr.PropertyType : typeof(object);
					}
				}

				#endregion
			}

			private readonly object _instance;

			public ExpandableTD(ICustomTypeDescriptor parent, object instance)
				: base(parent)
			{
				this._instance = instance;
			}

			public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
			{
				var a = base.GetProperties(attributes);
				var pda = new ExpandablePD[a.Count];
				for (var i = 0; i < pda.Length; i++)
				{
					pda[i] = new ExpandablePD(a[i]);
				}
				return new PropertyDescriptorCollection(pda, true);
			}

			public override PropertyDescriptorCollection GetProperties()
			{
				var a = base.GetProperties();
				var pda = new ExpandablePD[a.Count];
				for (var i = 0; i < pda.Length; i++)
				{
					pda[i] = new ExpandablePD(a[i]);
				}
				return new PropertyDescriptorCollection(pda, true);
			}
		}
	}
}